{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению. Сессия № 2\n",
    "Автор материала: программист-исследователь Mail.ru Group, старший преподаватель Факультета Компьютерных Наук ВШЭ Юрий Кашницкий. Материал распространяется на условиях лицензии [Creative Commons CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# <center>Тема 10. Бустинг\n",
    "## <center>Часть 6. Борьба с недообучением и переобучением Xgboost"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Построим кривые вадидации Xgboost по числу деревьев в задаче классификации синтетических данных.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "from scipy.sparse import vstack\n",
    "from sklearn.datasets import make_classification\n",
    "from sklearn.model_selection import StratifiedKFold, validation_curve\n",
    "from xgboost.sklearn import XGBClassifier"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Генерируем синтетические данные.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X, y = make_classification(\n",
    "    n_samples=1000,\n",
    "    n_features=20,\n",
    "    n_informative=8,\n",
    "    n_redundant=3,\n",
    "    n_repeated=2,\n",
    "    random_state=42,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Будем проводить 10-кратную стратифицированную кросс-валидацию. По умолчанию в статьях по машинному обучению и статистике используется именно такая.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cv = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Посмотрим, как число деревьев влияет на качество модели.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "default_params = {\n",
    "    \"objective\": \"binary:logistic\",\n",
    "    \"max_depth\": 1,\n",
    "    \"learning_rate\": 0.3,\n",
    "    \"silent\": 1.0,\n",
    "    \"seed\": 42,\n",
    "}\n",
    "\n",
    "n_estimators_range = np.linspace(1, 200, 10).astype(\"int\")\n",
    "\n",
    "train_scores, test_scores = validation_curve(\n",
    "    XGBClassifier(**default_params),\n",
    "    X,\n",
    "    y,\n",
    "    param_name=\"n_estimators\",\n",
    "    param_range=n_estimators_range,\n",
    "    cv=cv.split(X, y),\n",
    "    scoring=\"accuracy\",\n",
    "    n_jobs=-1,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Построим кривые валидации.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_scores_mean = np.mean(train_scores, axis=1)\n",
    "train_scores_std = np.std(train_scores, axis=1)\n",
    "test_scores_mean = np.mean(test_scores, axis=1)\n",
    "test_scores_std = np.std(test_scores, axis=1)\n",
    "\n",
    "fig = plt.figure(figsize=(10, 6), dpi=100)\n",
    "\n",
    "plt.title(\"Validation Curve with XGBoost (eta = 0.3)\")\n",
    "plt.xlabel(\"number of trees\")\n",
    "plt.ylabel(\"Accuracy\")\n",
    "plt.ylim(0.5, 1.01)\n",
    "\n",
    "plt.plot(n_estimators_range, train_scores_mean, label=\"Training score\", color=\"r\")\n",
    "\n",
    "plt.plot(\n",
    "    n_estimators_range, test_scores_mean, label=\"Cross-validation score\", color=\"g\"\n",
    ")\n",
    "\n",
    "plt.fill_between(\n",
    "    n_estimators_range,\n",
    "    train_scores_mean - train_scores_std,\n",
    "    train_scores_mean + train_scores_std,\n",
    "    alpha=0.2,\n",
    "    color=\"r\",\n",
    ")\n",
    "\n",
    "plt.fill_between(\n",
    "    n_estimators_range,\n",
    "    test_scores_mean - test_scores_std,\n",
    "    test_scores_mean + test_scores_std,\n",
    "    alpha=0.2,\n",
    "    color=\"g\",\n",
    ")\n",
    "\n",
    "plt.axhline(y=1, color=\"k\", ls=\"dashed\")\n",
    "\n",
    "plt.legend(loc=\"best\")\n",
    "plt.show()\n",
    "\n",
    "i = np.argmax(test_scores_mean)\n",
    "print(\n",
    "    \"Best cross-validation result ({0:.2f}) obtained for {1} trees\".format(\n",
    "        test_scores_mean[i], n_estimators_range[i]\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Что заметно:**\n",
    "\n",
    "- Доля правильных ответов на обучении продолжает расти, хотя на валидации - уже нет\n",
    "- Когда деревьев меньше 25 разброс (variance) ошибки невелик, но и смещение (bias) велико\n",
    "- Начиная с 25 деревьев, разброс ошибки начинает расти\n",
    "- Модель доаольно стабильна: дальнейшее увеличение ее сложности не приводит к увеличению разброса ошибки.\n",
    "\n",
    "Оптимальное в данном случае сочетание разброса и смещение наблюдается примерно при 50 итерациях. Хотя разброс все же велик."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Что можно улучшить?\n",
    "\n",
    "#### Снижение разброса\n",
    "Для уменьшения сложности модели можно:\n",
    "- использовать меньше признаков (например, отбор)\n",
    "- использовать больше объектов (например, искусственно созданных)\n",
    "- увеличить регуляризацию\n",
    "\n",
    "В случае XGBoost можно:\n",
    "- уменьшать максимальную глубину деревьев(`max_depth`)\n",
    "- увеличивать значение параметра `min_child_weight`\n",
    "- увеличивать значение параметра `gamma`\n",
    "- добавлять больше \"случайности\" за счет параметров `subsample` и `colsample_bytree`\n",
    "- увеличивать значение паарметров регуляризации `lambda` и `alpha`\n",
    "\n",
    "#### Снижение смещения\n",
    "Если модель слишком простая, можно:\n",
    "- добавлять больше признаков (например, изобретать их),\n",
    "- усложнять модель\n",
    "- уменьшать регуляризацию\n",
    "\n",
    "В случае XGBoost можно:\n",
    "- увеличивать максимальную глубину деревьев(`max_depth`)\n",
    "- уменьшать значение параметра `min_child_weight`\n",
    "- уменьшать значение параметра `gamma`\n",
    "- уменьшать значение параметров регуляризации `lambda` и `alpha`\n",
    "\n",
    "**Попробуем немного настроить параметры. Будем для построения каждого дерева использовать в среднем 70% случайно выбранных объектов и 60% случайно выбранных признаков. Это уменьшит разброс ошибки. Для уменьшения смещения увеличим на 1 максимальную глубину деревьев.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_params = {\n",
    "    \"objective\": \"binary:logistic\",\n",
    "    \"max_depth\": 2,  # changed\n",
    "    \"learning_rate\": 0.3,\n",
    "    \"silent\": 1.0,\n",
    "    \"colsample_bytree\": 0.6,  # added\n",
    "    \"subsample\": 0.7,  # added\n",
    "    \"seed\": 42,\n",
    "}\n",
    "\n",
    "n_estimators_range = np.linspace(1, 200, 10).astype(\"int\")\n",
    "\n",
    "train_scores, test_scores = validation_curve(\n",
    "    XGBClassifier(**new_params),\n",
    "    X,\n",
    "    y,\n",
    "    param_name=\"n_estimators\",\n",
    "    param_range=n_estimators_range,\n",
    "    cv=cv.split(X, y),\n",
    "    scoring=\"accuracy\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_scores_mean = np.mean(train_scores, axis=1)\n",
    "train_scores_std = np.std(train_scores, axis=1)\n",
    "test_scores_mean = np.mean(test_scores, axis=1)\n",
    "test_scores_std = np.std(test_scores, axis=1)\n",
    "\n",
    "fig = plt.figure(figsize=(10, 6), dpi=100)\n",
    "\n",
    "plt.title(\"Validation Curve with XGBoost (eta = 0.3)\")\n",
    "plt.xlabel(\"number of trees\")\n",
    "plt.ylabel(\"Accuracy\")\n",
    "plt.ylim(0.7, 1.1)\n",
    "\n",
    "plt.plot(n_estimators_range, train_scores_mean, label=\"Training score\", color=\"r\")\n",
    "\n",
    "plt.plot(\n",
    "    n_estimators_range, test_scores_mean, label=\"Cross-validation score\", color=\"g\"\n",
    ")\n",
    "\n",
    "plt.fill_between(\n",
    "    n_estimators_range,\n",
    "    train_scores_mean - train_scores_std,\n",
    "    train_scores_mean + train_scores_std,\n",
    "    alpha=0.2,\n",
    "    color=\"r\",\n",
    ")\n",
    "\n",
    "plt.fill_between(\n",
    "    n_estimators_range,\n",
    "    test_scores_mean - test_scores_std,\n",
    "    test_scores_mean + test_scores_std,\n",
    "    alpha=0.2,\n",
    "    color=\"g\",\n",
    ")\n",
    "\n",
    "plt.axhline(y=1, color=\"k\", ls=\"dashed\")\n",
    "\n",
    "plt.legend(loc=\"best\")\n",
    "plt.show()\n",
    "\n",
    "i = np.argmax(test_scores_mean)\n",
    "print(\n",
    "    \"Best cross-validation result ({0:.2f}) obtained for {1} trees\".format(\n",
    "        test_scores_mean[i], n_estimators_range[i]\n",
    "    )\n",
    ")"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
